import { z, ZodSchema, ZodError, ZodTypeAny } from 'zod';
import { ValidationError } from '@/types';

/**
 * Message validation utilities
 */

/**
 * Common validation schemas
 */
export const CommonSchemas = {
  // Basic types
  uuid: z.string().uuid(),
  email: z.string().email(),
  url: z.string().url(),
  timestamp: z.string().datetime(),

  // Message headers
  messageHeaders: z.record(z.union([z.string(), z.number(), z.boolean()])),

  // Message properties
  messageProperties: z.record(z.unknown()),

  // Basic message structure
  baseMessage: z.object({
    id: z.string(),
    timestamp: z.number(),
    headers: z.record(z.union([z.string(), z.number(), z.boolean()])).optional(),
    properties: z.record(z.unknown()).optional(),
  }),
};

/**
 * Schema registry for managing validation schemas
 */
export class SchemaRegistry {
  private readonly schemas = new Map<string, ZodTypeAny>();

  /**
   * Register a schema
   */
  register<T>(name: string, schema: ZodTypeAny): void {
    this.schemas.set(name, schema);
  }

  /**
   * Get a schema by name
   */
  get<T>(name: string): z.ZodSchema<T> | undefined {
    return this.schemas.get(name) as z.ZodSchema<T> | undefined;
  }

  /**
   * Validate data against a registered schema
   */
  validate<T>(name: string, data: unknown): T {
    const schema = this.get<T>(name);
    if (!schema) {
      throw new ValidationError(`Schema '${name}' not found`);
    }

    try {
      return schema.parse(data);
    } catch (error) {
      if (error instanceof z.ZodError) {
        const issues = error.issues
          .map(issue => `${issue.path.join('.')}: ${issue.message}`)
          .join(', ');
        throw new ValidationError(`Validation failed for schema '${name}': ${issues}`);
      }
      throw error;
    }
  }

  /**
   * Check if data is valid against a schema
   */
  isValid(name: string, data: unknown): boolean {
    try {
      this.validate(name, data);
      return true;
    } catch {
      return false;
    }
  }

  /**
   * Get all registered schema names
   */
  getSchemaNames(): string[] {
    return Array.from(this.schemas.keys());
  }

  /**
   * Clear all schemas
   */
  clear(): void {
    this.schemas.clear();
  }
}

/**
 * Message validator class
 */
export class MessageValidator {
  private readonly registry: SchemaRegistry;

  constructor(registry?: SchemaRegistry) {
    this.registry = registry ?? new SchemaRegistry();
    this.registerCommonSchemas();
  }

  /**
   * Register common schemas
   */
  private registerCommonSchemas(): void {
    Object.entries(CommonSchemas).forEach(([name, schema]) => {
      this.registry.register(name, schema as ZodTypeAny);
    });
  }

  /**
   * Validate message structure
   */
  validateMessage(data: unknown): void {
    this.registry.validate('baseMessage', data);
  }

  /**
   * Validate message headers
   */
  validateHeaders(headers: unknown): void {
    this.registry.validate('messageHeaders', headers);
  }

  /**
   * Validate against custom schema
   */
  validate<T>(schemaName: string, data: unknown): T {
    return this.registry.validate<T>(schemaName, data);
  }

  /**
   * Register a custom schema
   */
  registerSchema<T>(name: string, schema: z.ZodSchema<T>): void {
    this.registry.register(name, schema);
  }

  /**
   * Get the schema registry
   */
  getRegistry(): SchemaRegistry {
    return this.registry;
  }
}

/**
 * Validation decorators
 */

/**
 * Validate method parameters
 */
export function validateParams(schemas: Record<string, z.ZodSchema>) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = function (...args: any[]) {
      const paramNames = Object.keys(schemas);

      paramNames.forEach((paramName, index) => {
        if (index < args.length) {
          const schema = schemas[paramName];
          try {
            args[index] = schema.parse(args[index]);
          } catch (error) {
            if (error instanceof z.ZodError) {
              const issues = error.issues
                .map(issue => `${paramName}.${issue.path.join('.')}: ${issue.message}`)
                .join(', ');
              throw new ValidationError(`Parameter validation failed: ${issues}`);
            }
            throw error;
          }
        }
      });

      return originalMethod.apply(this, args);
    };

    return descriptor;
  };
}

/**
 * Validate method return value
 */
export function validateReturn<T>(schema: z.ZodSchema<T>) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = async function (...args: any[]) {
      const result = await originalMethod.apply(this, args);

      try {
        return schema.parse(result);
      } catch (error) {
        if (error instanceof z.ZodError) {
          const issues = error.issues
            .map(issue => `${issue.path.join('.')}: ${issue.message}`)
            .join(', ');
          throw new ValidationError(`Return value validation failed: ${issues}`);
        }
        throw error;
      }
    };

    return descriptor;
  };
}

/**
 * Utility functions
 */

/**
 * Create a validator with common schemas
 */
export function createValidator(): MessageValidator {
  return new MessageValidator();
}

/**
 * Create a schema registry
 */
export function createSchemaRegistry(): SchemaRegistry {
  return new SchemaRegistry();
}

/**
 * Validate data against a schema
 */
export function validate<T>(schema: z.ZodSchema<T>, data: unknown): T {
  try {
    return schema.parse(data);
  } catch (error) {
    if (error instanceof z.ZodError) {
      const issues = error.issues
        .map(issue => `${issue.path.join('.')}: ${issue.message}`)
        .join(', ');
      throw new ValidationError(`Validation failed: ${issues}`);
    }
    throw error;
  }
}

/**
 * Check if data is valid against a schema
 */
export function isValid<T>(schema: z.ZodSchema<T>, data: unknown): boolean {
  try {
    schema.parse(data);
    return true;
  } catch {
    return false;
  }
}

/**
 * Transform and validate data
 */
export function transform<T, U>(
  schema: z.ZodSchema<T>,
  transformer: (data: T) => U,
  data: unknown
): U {
  const validatedData = validate(schema, data);
  return transformer(validatedData);
}

/**
 * Common validation patterns
 */
export const ValidationPatterns = {
  /**
   * User event schema
   */
  userEvent: z.object({
    userId: CommonSchemas.uuid,
    eventType: z.enum(['login', 'logout', 'signup', 'purchase', 'view']),
    timestamp: CommonSchemas.timestamp,
    metadata: z.record(z.unknown()).optional(),
  }),

  /**
   * Order schema
   */
  order: z.object({
    orderId: CommonSchemas.uuid,
    customerId: CommonSchemas.uuid,
    amount: z.number().positive(),
    currency: z.string().length(3),
    items: z.array(
      z.object({
        productId: z.string(),
        quantity: z.number().int().positive(),
        price: z.number().positive(),
      })
    ),
    createdAt: CommonSchemas.timestamp,
  }),

  /**
   * Notification schema
   */
  notification: z.object({
    id: CommonSchemas.uuid,
    type: z.enum(['email', 'sms', 'push', 'webhook']),
    recipient: z.string(),
    subject: z.string().optional(),
    body: z.string(),
    priority: z.enum(['low', 'normal', 'high', 'urgent']).default('normal'),
    scheduledAt: CommonSchemas.timestamp.optional(),
    metadata: z.record(z.unknown()).optional(),
  }),

  /**
   * Task schema
   */
  task: z.object({
    id: CommonSchemas.uuid,
    type: z.string(),
    payload: z.record(z.unknown()),
    priority: z.number().int().min(0).max(10).default(5),
    maxRetries: z.number().int().min(0).default(3),
    timeout: z.number().int().positive().default(30000),
    createdAt: CommonSchemas.timestamp,
    scheduledAt: CommonSchemas.timestamp.optional(),
  }),
};

// Global validator instance
export const globalValidator = createValidator();

// Register common patterns
Object.entries(ValidationPatterns).forEach(([name, schema]) => {
  globalValidator.registerSchema(name, schema as ZodTypeAny);
});
